package Model;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Random;

/**
 * This class is used for the Game Board
 */
public class Board extends Observable {

    // ATTRIBUTE
    private int width;
    private int height;
    private Square[][] gameBoard;

    // CONSTRUCTOR
    public Board(int width, int height) {
        this.width = width;
        this.height = height;
        gameBoard = new Square[width][height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                gameBoard[i][j] = new Square(new Location(i, j), SquareType.Plain);
            }
        }

    }

    // OWN FUNCTIONS
    // persistence must be between 0 and 1
    public void initGameBoard(int step, int nbOctaves, double persistence, double[] bounds) {
        if (bounds.length != 8) {
            System.out.println("Error in bounds : the tab must be of 8 elements");
            return;
        }

        TerrainGenerator tg = new TerrainGenerator(step, this.width, this.height, nbOctaves);
        double v;

        for (int i = 0; i < this.width; i++) {
            for (int j = 0; j < this.height; j++) {
                //System.out.println(i + ", " + j);
                v = tg.coherentNoise(i, j, persistence);

                if (v <= bounds[0]) {
                    gameBoard[i][j].setType(SquareType.Water);
                } else if (v > bounds[1] && v <= bounds[2]) {
                    gameBoard[i][j].setType(SquareType.Mountain);
                } else if (v > bounds[3] && v <= bounds[4]) {
                    gameBoard[i][j].setType(SquareType.Stone);
                    gameBoard[i][j].setResource(new Resource(ResourceType.Stone, 50));
                } else if (v > bounds[5] && v <= bounds[6]) {
                    gameBoard[i][j].setType(SquareType.Gold);
                    gameBoard[i][j].setResource(new Resource(ResourceType.Gold, 50));
                } else if (v >= bounds[7]) {
                    gameBoard[i][j].setType(SquareType.Wood);
                    gameBoard[i][j].setResource(new Resource(ResourceType.Wood, 50));
                }
            }
        }
    }

    public ArrayList<Square> getNeighbours(int radius, Location location) {
        ArrayList<Square> res = new ArrayList<Square>();
        int windowSize = 2 * radius + 1;
        for (int j = 0; j < windowSize; j++) {
            for (int i = 0; i < windowSize; i++) {
                int x = i - radius + location.getXPosition();
                int y = j - radius + location.getYPosition();
                if ((0 <= x)
                        && (x < this.width)
                        && (0 <= y)
                        && (y < this.height)) {
                    Location locationToAdd = new Location(x, y);
                    res.add(this.getSquareFromLocation(locationToAdd));
                }
            }
        }
        return res;
    }

    public void displayBoardInTerminal() {
        for (int j = 0; j < this.height; j++) {
            for (int i = 0; i < this.width; i++) {
                gameBoard[i][j].displayLongInfoInTerminal();
            }
            System.out.println();
        }
    }

    public Square getSquareFromLocation(Location location) {
        if (location.getXPosition() < 0 || location.getXPosition() > (width - 1)
                || location.getYPosition() < 0 || location.getYPosition() > (height - 1)) {
            System.out.println("=========Tentative d'acces a la position inexistante  suivante============");
            location.displayInTerminal();
            System.out.println("=========Brace yourself, ArrayIndexOutOfBoundsExceptionnull is coming ! ==");
            return null;
        }

        return this.gameBoard[location.getXPosition()][location.getYPosition()];
    }

    public Location getRandomFreeLocation() {
        Square randomSquare;
        int randomWidth;
        int randomHeight;

        do {
            randomWidth = (int) (Math.random() * this.width);
            randomHeight = (int) (Math.random() * this.height);

            randomSquare = gameBoard[randomWidth][randomHeight];
        } while (randomSquare.isObstacle());

        return randomSquare.getLocation();
    }

    /**
     * Return the nearest free Location from QG
     */
    public Location getFreeLocation(Location QG) {
        Location freeLocation = getRandomFreeLocation();
        /*boolean free = false;
         int i = QG.getXPosition();
         int j = QG.getYPosition();
         int step = 0;
         Location freeLocation = null;
         while (!free) {
         for (int ii = -step; ii <= step; ii++) {
         for (int jj = -step; jj <= step; jj++) {
         int x = i + ii;
         int y = j + jj;
         if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
         if (!this.gameBoard[x][y].isObstacle()) {
         freeLocation = new Location(x, y);
         free = true;
         }
         }
         }
         }
         step++;
         }*/
        return freeLocation;
    }

    /**
     * Return an array list of random location where to place nbVillagers around
     * the QG location at the beginning
     */
    public ArrayList<Location> placeVillagersAtBeginning(Location QG, int nbVillagers) {
        int x = QG.getXPosition();
        int y = QG.getYPosition();
        int campSize = (int) Math.ceil(Math.sqrt(nbVillagers));
        if (campSize % 2 == 0) {
            campSize += 1;
        }
        int halfCampSize = (int) Math.floor(campSize / 2);
        // Transform the camp in Plain
        for (int i = x - halfCampSize; i <= x + halfCampSize; i++) {
            for (int j = y - halfCampSize; j <= y + halfCampSize; j++) {
                this.gameBoard[i][j].setType(SquareType.Plain);
            }
        }

        // Find nbVillagers different random locations around the QG
        int l;
        int c;
        Location loc;
        ArrayList<Location> villagersLocations = new ArrayList<Location>();
        boolean free;
        for (int n = 0; n < nbVillagers; n++) {
            do {
                l = x - halfCampSize + new Random().nextInt(campSize);
                c = y - halfCampSize + new Random().nextInt(campSize);

                int cpt = 0;
                for (int k = 0; k < villagersLocations.size(); k++) {
                    if (villagersLocations.get(k).getXPosition() == l
                            && villagersLocations.get(k).getYPosition() == c) {
                        cpt += 1;
                    }
                }
                free = (cpt == 0);
            } while (!free);
            loc = new Location(l, c);
            villagersLocations.add(loc);
        }

        return villagersLocations;
    }

    /**
     * Return the path between the location position and the location QG
     */
    /* public ArrayList<Location> getPath(Location position, Location QG) 
     {
     //NEW VERSION
     /*ArrayList<Location> path = new ArrayList<Location>();
     int x1 = position.getXPosition();
     int y1 = position.getYPosition();
     int x2 = QG.getXPosition();
     int y2 = QG.getYPosition();        
        
     double length;
     if (Math.abs(x2-x1) >= Math.abs(y2-y1))
     length = Math.abs(x2-x1);
     else
     length = Math.abs(y2-y1);
        
     double dx = (x2-x1) / length;
     double dy = (y2-y1) / length;

     //System.out.println(y2-y1);
     //System.out.println(length);
     //System.out.println(dy);
        
     double x = x1 + 0.5*Math.signum(dx);
     double y = y1 + 0.5*Math.signum(dy);
     int i = 1;
     Location l;
     while (i <= length)
     {
     l = new Location((int)x,(int)y);
     path.add(l);
     x += dx;
     y += dy;
     i++;
     }

     return path;
       
     }*/
    public ArrayList<Location> getPath(Location position, Location QG) {
        ArrayList<Location> path = new ArrayList<Location>();
        path.add(position);
        int x1 = position.getXPosition();
        int y1 = position.getYPosition();
        int x2 = QG.getXPosition();
        int y2 = QG.getYPosition();
        int dx = x2 - x1;
        int dy = y2 - y1;
        Location l = new Location(x1, y1);

        int ii = 0;
        int jj = 0;

        while (l.getXPosition() != x2 || l.getYPosition() != y2) {
            if (dx > 0 && ii < dx) {
                ii++;
            }
            if (dx < 0 && ii > dx) {
                ii--;
            }
            if (dy > 0 && jj < dy) {
                jj++;
            }
            if (dy < 0 && jj > dy) {
                jj--;
            }

            l = new Location(x1 + ii, y1 + jj);
            path.add(l);
        }

        return path;
    }

    public void resetAllBeingsReference() {
        for (int j = 0; j < this.height; j++) {
            for (int i = 0; i < this.width; i++) {
                this.gameBoard[i][j].setBeingReference(null);
            }
        }
    }

    public boolean updatePosition(Location oldPosition, Being being) {
        if (this.getSquareFromLocation(oldPosition).getBeingReference() == null) {
            System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ERREUR UPDATE BOARD %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
            System.out.println("%%%%%%%% Being ID: " + being.getId() + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
            System.out.println("Board old position : " + oldPosition.toString());
            System.out.println("Board new position : " + being.getLocation().toString());
            System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%%%% NO BEING IN THE OLD POSITION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
            return false;

        } else if (being.getId() != this.getSquareFromLocation(oldPosition).getBeingReference().getId()) {
            System.out.println("__________________________________________________________________________________");
            System.out.println("%%%%%%%% Being ID: " + being.getId() + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
            System.out.println("%%%%%%%% Being in board ID: " + this.getSquareFromLocation(oldPosition).getBeingReference().getId() + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
            System.out.println("__________________________________________________________________________________");
            return false;

        } else if (this.getSquareFromLocation(being.getLocation()).getBeingReference() != null) {
            System.out.println("______________POSITION ALREADY FULL   _________________");
            System.out.println("Being ID: " + being.getId());
            System.out.println("Being in board ID: " + this.getSquareFromLocation(being.getLocation()).getBeingReference().getId());
            System.out.println("isObstacle : " + this.getSquareFromLocation(being.getLocation()).isObstacle());
            System.out.println("__________________________________________________________________________________");
            return false;
        } else {
            Square s = this.getSquareFromLocation(oldPosition);
            s.removeBeing();

            if (this.getSquareFromLocation(oldPosition).getBeingReference() != null) {
                System.out.println("!!!!!!!!!!!!!!!! Remove KO !!!!!!!!!!!!!!!!!!!!!");
            }

            this.getSquareFromLocation(being.getLocation()).setBeingReference(being);

            BeingActionEvent bae = new BeingActionEvent(this);
            bae.init("Move", oldPosition, being.getLocation());

            setChanged();
            notifyObservers(bae);
            return true;
        }



    }

    public void applyAction(DirectDirection direction, Being being) {
        BeingActionEvent bae;
        Square targetSquare = this.getSquareFromLocation(being.getLocation().getLocationInDirectDirection(direction));

        if (being.isAnimal()) {
            System.out.println("Apply Action for animal");
            Animal a = (Animal) being;
            if (a.getType().equals(AnimalType.Lion)) {
                System.out.println("Apply Action for lion");
                targetSquare.getBeingReference().kill();

                //Notify View
                bae = new BeingActionEvent(this);
                bae.init("LionKill", targetSquare.getLocation(), being.getLocation(), a.getType());

                targetSquare.setBeingReference(null);
                bae.setRemoveTarget(true);


                setChanged();
                notifyObservers(bae);

            }
            return;
        }

        switch (being.activity) {
            case Hunter:
                //Maj Model
                targetSquare.getBeingReference().kill();

                //Notify View
                bae = new BeingActionEvent(this);
                bae.init("Kill", targetSquare.getLocation(), being.getLocation(), being.getActivity(), ((Animal) targetSquare.getBeingReference()).getType());

                if (targetSquare.getBeingReference().getResourceBrought().getAmount() == 0) {
                    // if no more resource in Square --> destroy being in actionSquare
                    ((Villager) being).getMyClan().getMemory().removeAnimalWithID(Activity.Hunter, targetSquare.getBeingReference().getId());

                    targetSquare.setBeingReference(null);
                    bae.setRemoveTarget(true);
                }

                setChanged();
                notifyObservers(bae);
                break;
            case Goldminer:
            case Stoneminer:
            case Lumberjack:
                bae = new BeingActionEvent(this);
                bae.init("Pickup", targetSquare.getLocation(), being.getLocation(), being.getActivity());

                if (targetSquare.getResource().getAmount() == 0) {
                    // if no more resource in Square --> destroy resource in actionSquare
                    targetSquare.setResource(null);
                    targetSquare.setType(SquareType.Plain);
                    bae.setRemoveTarget(true);

                    //To do, enlever de memory
                }
                setChanged();
                notifyObservers(bae);
                break;
        }
    }

    public void notifyDropResource(Location l, Activity a) {
        BeingActionEvent bae = new BeingActionEvent(this);
        bae.init("Drop", l, a);
        setChanged();
        notifyObservers(bae);

    }

    // GETTER - SETTER
    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public Square[][] getGameBoard() {
        return gameBoard;
    }

    public void setGameBoard(Square[][] gameBoard) {
        this.gameBoard = gameBoard;
    }
}
